/* ************************************************************************ */
/*																			*/
/*  haXe Video 																*/
/*  Copyright (c)2007-2008 Russell Weir, Nicolas Cannasse						*/
/*																			*/
/* This library is free software; you can redistribute it and/or			*/
/* modify it under the terms of the GNU Lesser General Public				*/
/* License as published by the Free Software Foundation; either				*/
/* version 2.1 of the License, or (at your option) any later version.		*/
/*																			*/
/* This library is distributed in the hope that it will be useful,			*/
/* but WITHOUT ANY WARRANTY; without even the implied warranty of			*/
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU		*/
/* Lesser General Public License or the LICENSE file for more details.		*/
/*																			*/
/* ************************************************************************ */


This format is not complete


package format;

enum Amf3Value {
	A3Number( f : Float );
	A3Integer( i : Int );
	A3Bool( b : Bool );
	A3String( idx: Int, s : String );
	A3Object( fields : Hash<AmfValue>, ?size : Int );
	A3Date( d : Date );
	A3Undefined;
	A3Null;
}

class Amf3 {

	public static function readWithCode( i : neko.io.Input, id ) {
		return switch( id ) {
		case 0x00:
			A3Undefined;
		case 0x01:
			A3Null;
		case 0x02:
			A3Bool(false);
		case 0x03:
			A3Bool(true);
		case 0x04:
			var n : Int = 0;
			for(x in 0...4) {
				n = n << 7;
				var c = i.readChar();
				if(x == 3) {
					n = n | c;
				}
				else {
					if(c & 0x80) {
						n = n | (c & 0x7F);
					}
					else {
						n = n | c ;
						break;
					}
				}
			}
			A3Integer( n );
		case 0x05:
			A3Number( i.readDoubleB() );

		/ VV not complete VV */
		case 0x06:
			A3String( i.read(i.readUInt16B()) );
		case 0x08:
			var time_ms = i.readDoubleB();
			var tz_min = i.readUInt16B();
			A3Date( Date.fromTime(time_ms + tz_min * 60 * 1000.0) );
		case 0x0A:
			var h = new Hash();
			var ismixed = (id == 0x08);
			var size = if( ismixed ) i.readUInt32B() else null;
			while( true ) {
				var c1 = i.readChar();
				var c2 = i.readChar();
				var name = i.read((c1 << 8) | c2);
				var k = i.readChar();
				if( k == 0x09 )
					break;
				h.set(name,readWithCode(i,k));
			}
			A3Object(h,size);

		case 0x07:
			throw "Not supported : Reference";
		case 0x0C:
			A3String( i.read(i.readUInt32B()) );
		default:
			throw "Unknown AMF "+id;
		}
	}

	public static function read( i : neko.io.Input ) {
		return readWithCode(i,i.readChar());
	}

	public static function write( o : neko.io.Output, v : AmfValue ) {
		switch( v ) {
		case A3Undefined:
			o.writeChar(0x00);
		case A3Null:
			o.writeChar(0x01);
		case A3Bool(b):
			o.writeChar(if( b ) 0x3 else 0x2);
		case A3Integer(n):
			o.writeChar(0x04);
			if(n <= 0x7F)
				o.writeChar(n);
			else if(n <= 0x3FFF) {
				o.writeChar(n >> 7 & 0x7F | 0x80);
				o.writeChar(n & 0x7F);
			}
			else if(n <= 0x1FFFFF) {
				o.writeChar(n >> 14 & 0x7F | 0x80);
				o.writeChar(n >> 7 & 0x7F | 0x80);
				o.writeChar(n & 0x7F);
			}
			else if(n <= 0x3FFFFFFF) {
				o.writeChar(n >> 21 & 0x7F | 0x80);
				o.writeChar(n >> 14 & 0x7F | 0x80);
				o.writeChar(n >> 7 & 0x7F | 0x80);
				o.writeChar(n & 0xFF);
			}
			else {
				throw "Integer overflow";
			}
		case A3Number(n):
			o.writeChar(0x05);
			o.writeDoubleB(n);

		/ VV not complete VV */
		case A3String(s):
			if( s.length <= 0xFFFF ) {
				o.writeChar(0x02);
				o.writeUInt16B(s.length);
			} else {
				o.writeChar(0x0C);
				o.writeUInt32B(s.length);
			}
			o.write(s);
		case A3Object(h,size):
			if( size == null )
				o.writeChar(0x03);
			else {
				o.writeChar(0x08);
				o.writeUInt32B(size);
			}
			for( f in h.keys() ) {
				o.writeUInt16B(f.length);
				o.write(f);
				write(o,h.get(f));
			}
			o.writeChar(0);
			o.writeChar(0);
			o.writeChar(0x09);


		case A3Date(d):
			o.writeDoubleB(d.getTime());
			o.writeUInt16B(0); // lose TZ
		}
	}

	public static function encode( o : Dynamic ) {
		return switch( Type.typeof(o) ) {
		case TNull: A3Null;
		case TInt: A3Number(o);
		case TFloat: A3Number(o);
		case TBool: A3Bool(o);
		case TObject:
			var h = new Hash();
			for( f in Reflect.fields(o) )
				h.set(f,encode(Reflect.field(o,f)));
			A3Object(h);
		case TClass(c):
			switch( c ) {
			case cast String:
				A3String(o);
			case cast Hash:
				var o : Hash<Dynamic> = o;
				var h = new Hash();
				for( f in o.keys() )
					h.set(f,encode(o.get(f)));
				A3Object(h);
			default:
				throw "Can't encode instance of "+Type.getClassName(c);
			}
		default:
			throw "Can't encode "+Std.string(o);
		}
	}

	public static function number( a : AmfValue ) {
		if( a == null ) return null;
		return switch( a ) {
		case A3Number(n): n;
		default: null;
		}
	}

	public static function string( a : AmfValue ) {
		if( a == null ) return null;
		return switch( a ) {
		case A3String(s): s;
		default: null;
		}
	}

	public static function object( a : AmfValue ) {
		if( a == null ) return null;
		return switch( a ) {
		case A3Object(o,_): o;
		default: null;
		}
	}

	public static function bool( a : AmfValue ) {
		if( a == null ) return null;
		return switch( a ) {
		case A3Bool(b): b;
		default: null;
		}
	}

}
}